Skip to content

Pages (Topics) CRUD management in Grand.Web.Store#693

Merged
KrzysztofPajak merged 18 commits into
developfrom
copilot/implement-page-management-ui
May 12, 2026
Merged

Pages (Topics) CRUD management in Grand.Web.Store#693
KrzysztofPajak merged 18 commits into
developfrom
copilot/implement-page-management-ui

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 10, 2026

  • Create PageController.cs in Grand.Web.Store/Controllers/
  • Create views in Grand.Web.Store/Areas/Store/Views/Page/
  • Add @using Grand.Web.AdminShared.Models.Pages to _ViewImports.cshtml
  • Add Copy button & action on Edit page (for multistore/no-store topics)
  • Add EN translations for new keys to DefaultLanguage.xml
  • Fix ArgumentNullException on Copy: null-safe seoSettings.ReservedEntityUrlSlugs; add regression test
  • Two-tab List view: "Store Pages" + "Global Pages" tabs
  • Fix pagination in Grand.Web.Store: apply Skip((page-1)*pageSize).Take(pageSize) so each page returns its correct slice
  • Fix pagination in Grand.Web.Admin controller: same Skip/Take fix applied to PageController.List
  • Fix pagination bar hidden in Grand.Web.Admin Page list: inject AdminAreaSettings, add pageSize, restore standard pageable config with pageSizes
  • Grand.Web.Store list: remove fixed width from Title column so it fills available horizontal space
  • Grand.Web.Admin list: add missing Title column after SystemName
  • Grand.Web.Admin list: replace IsPasswordProtected with IncludeInMenu and Published (Visible) columns

Copilot AI and others added 2 commits May 10, 2026 18:04
- Add PageController with CRUD operations, filtering by store, and
  automatic storeId assignment on create/edit
- Add List, Create, Edit views with Partials (TabInfo, TabSeo)
- Add Grand.Web.AdminShared.Models.Pages namespace to _ViewImports.cshtml"

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/e126ff73-2407-4da7-ae12-fd982e28a981

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
- Remove unused NoAccessToPageMessage constant
- Simplify redundant LimitedToStores condition in Edit GET
- Remove unnecessary ModelState check in Delete action

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/e126ff73-2407-4da7-ae12-fd982e28a981

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Web.Store - If the topic is multistore or without set as a store, Add new button on the form EDIT with the name Copy, and add action copy current topic to the new one for the current store (but only if this topic name does not exists in the current store)

When a topic is multistore (LimitedToStores + multiple stores) or has no
store restriction, a "Copy" button is shown on the Edit page. Clicking
it creates a store-specific copy (all fields + locales preserved) for
the current store, guarded by a duplicate SystemName check. Redirects
to the new page's Edit view on success.

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/623ad074-c18a-43f8-9a53-00b7f6df747c

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 11, 2026

@copilot Grand.Web.Store - If the topic is multistore or without set as a store, Add new button on the form EDIT with the name Copy, and add action copy current topic to the new one for the current st...

Done in commit 7effd7c.

Copy button behaviour:

  • The Copy button appears on the Edit form only when the topic is multistore (LimitedToStores + Stores.Count > 1) or has no store restriction (!LimitedToStores).
  • Clicking it POSTs to the new Copy action which:
    1. Refuses to copy a topic that is already single-store restricted.
    2. Checks whether a page with the same SystemName already exists for the current store (excluding the original). Shows an error and stays on the edit page if so.
    3. Creates a full copy — all fields and localized content preserved — limited exclusively to the current store (LimitedToStores = true, Stores = [storeId]).
    4. Redirects the store manager to the new page's Edit view.

The button uses the HTML5 form= attribute to reference a small standalone form rendered outside the main edit form, keeping the markup valid.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot please add new translation to default language EN

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot After click Copy I've got error
ArgumentNullException: Value cannot be null. (Parameter 'source')
System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
System.Linq.Enumerable.Contains(IEnumerable source, TSource value, IEqualityComparer comparer)
Grand.Business.Common.Services.Seo.SeNameService.ValidateSeName(T entity, string seName, string name, bool ensureNotEmpty) in SeNameService.cs
+
var reserved2 = seoSettings.ReservedEntityUrlSlugs.Contains(tempSeName, StringComparer.OrdinalIgnoreCase);
Grand.Web.AdminShared.Services.PageViewModelService.InsertPageModel(PageModel model) in PageViewModelService.cs
+
page.SeName = await _seNameService.ValidateSeName(page, model.SeName, page.Title ?? page.SystemName, true);
Grand.Web.Store.Controllers.PageController.Copy(string id) in PageController.cs
+
var newPage = await _pageViewModelService.InsertPageModel(model);

- admin.common.copy: Copy
- admin.content.pages.copy.duplicatesystemname: duplicate SystemName error
- admin.content.pages.permissions: multistore warning
- Update PageController to use translation resource instead of hardcoded string

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/e594e525-bd03-400b-9b1f-f634b62e6301

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 11, 2026

@copilot please add new translation to default language EN

Done in commit 7f0366b. Added 3 new entries to DefaultLanguage.xml (all in alphabetical order):

Key Value
admin.common.copy Copy
admin.content.pages.copy.duplicatesystemname A page with the same system name already exists for this store.
admin.content.pages.permissions Warning: This page is shared across multiple stores. Changes may affect other stores.

The hardcoded error string in PageController.Copy was also replaced with _translationService.GetResource("Admin.Content.Pages.Copy.DuplicateSystemName").

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj 53 -j ACCEPT administration/roxy_fileman/lang/es.json (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj 53 -j ACCEPT administration/roxy_fileman/lang/es.json (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj 53 -j ACCEPT administration/roxy_fileman/lang/es.json (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj 53 -j ACCEPT administration/roxy_fileman/lang/es.json (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

The property can be null when loaded from MongoDB in older installations.
Use null-conditional operator to avoid ArgumentNullException when Copy
(or any page insert) is triggered.

Also adds a regression test for the null-settings case.

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/e444e392-5111-4a1b-a6a1-297c8f3f5c98

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 11, 2026

@copilot After click Copy I've got error
ArgumentNullException: Value cannot be null. (Parameter 'source')
System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
...

Fixed in commit c0dbedf.

seoSettings.ReservedEntityUrlSlugs can be null when the property was stored as null in MongoDB (older installation). The Copy action copied the original page's SeName verbatim — since that SeName is already registered as a slug for another entity, ValidateSeName entered its deduplication loop and hit the null list before any admin-area path had done so.

The fix is a one-character null-conditional in SeNameService.ValidateSeName:

// before
var reserved2 = seoSettings.ReservedEntityUrlSlugs.Contains(tempSeName, ...);
// after
var reserved2 = seoSettings.ReservedEntityUrlSlugs?.Contains(tempSeName, ...) ?? false;

A regression test covering the null-settings case was added to SeNameServiceTests.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Web.Store - maybe it's better to prepare 2 tabs, one with landing pages for the current store and second tab for global landing pages, it's easier to see with pages contains to your store. But I think you need to change filtering service

- Controller: replace single List POST with StorePagesList + GlobalPagesList POST actions
  - StorePagesList: pages exclusively limited to this store (LimitedToStores && Stores.Count == 1)
  - GlobalPagesList: global (no-store) or multistore pages (!LimitedToStores || Stores.Count > 1)
- List.cshtml: two-tab layout using admin-tabstrip with separate Kendo grids per tab
- DefaultLanguage.xml: add admin.content.pages.list.storepages and admin.content.pages.list.globalpages

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/5f8af9bf-18a7-4d55-b50b-0f6aa8fdf36f

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 12, 2026

@copilot Grand.Web.Store - maybe it's better to prepare 2 tabs, one with landing pages for the current store and second tab for global landing pages, it's easier to see with pages contains to your sto...

Done in commit 7972cb6. The List page now shows two tabs:

  • Store Pages — pages exclusively assigned to the current store (LimitedToStores = true and only one store assigned). These are fully editable by the store manager.
  • Global Pages — pages with no store restriction or shared across multiple stores (!LimitedToStores || Stores.Count > 1). These show a warning on Edit and offer the Copy button so the manager can create their own store-specific copy.

Both tabs share the same name-filter search bar and have identical columns (SystemName, Title, IncludeInMenu, Published, DisplayOrder). No change was needed in the PageService because GetAllPages(storeId, ignoreAcl: true) already returns both categories as a cached list — the split is done in-memory on that cached result.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot pagination does not work well, second page show the same result as first page

…lobalPagesList

The controller was returning all records with serverPaging: true declared in the
Kendo grid, causing every page to show the same items (no slicing was applied).
Fix: compute Total from the full filtered list, then slice with Skip/Take before
returning Data.

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/1e4e597f-a4f8-4d36-8f4d-80401dcfcbec

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 12, 2026

@copilot pagination does not work well, second page show the same result as first page

Fixed in commit 0093d26. Both StorePagesList and GlobalPagesList were returning the full filtered list in Data while serverPaging: true was set — so Kendo always got back every record regardless of what page was requested. The fix computes Total from the full filtered list and then slices with Skip((page-1)*pageSize).Take(pageSize) before returning Data.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Web.Admin - on list of landing pages - pagination does not exists, the bar is hidden

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/22793ceb-63fd-4100-a7d2-24dc020da864

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 12, 2026

@copilot Grand.Web.Admin - on list of landing pages - pagination does not exists, the bar is hidden

Fixed in commit 98c37d9. The admin Page list view had the wrong Kendo pageable config (previousNext: false, numeric: false, info: false) which hid the pagination bar entirely. Applied the standard pattern used across all other admin list views: injected AdminAreaSettings, added pageSize: @(adminAreaSettings.DefaultGridPageSize) to the dataSource, and replaced the broken pageable config with pageSizes: [@(adminAreaSettings.GridPageSizes)].

@KrzysztofPajak KrzysztofPajak marked this pull request as ready for review May 12, 2026 03:39
Copilot AI review requested due to automatic review settings May 12, 2026 03:39
@KrzysztofPajak KrzysztofPajak changed the title feat: Pages (Topics) CRUD management in Grand.Web.Store Pages (Topics) CRUD management in Grand.Web.Store May 12, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Store-area CRUD management for CMS Pages (Topics), aligns list paging behavior across Store/Admin, and hardens SEO slug validation against null stored settings.

Changes:

  • Introduces Grand.Web.Store Page CRUD (controller + views), including a two-tab list (“Store Pages” / “Global Pages”) and a Copy workflow.
  • Fixes Kendo grid pagination in both Store and Admin Page lists by returning the correct Skip/Take slice plus total count.
  • Makes SeNameService.ValidateSeName null-safe for SeoSettings.ReservedEntityUrlSlugs and adds a regression test.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Web/Grand.Web.Store/Controllers/PageController.cs New Store-area Page CRUD endpoints (list, create/edit/delete, copy) and server-side paging for grids
src/Web/Grand.Web.Store/Areas/Store/Views/Page/Partials/CreateOrUpdate.TabSeo.cshtml New SEO tab UI for Page create/edit
src/Web/Grand.Web.Store/Areas/Store/Views/Page/Partials/CreateOrUpdate.TabInfo.cshtml New Info tab UI for Page create/edit
src/Web/Grand.Web.Store/Areas/Store/Views/Page/Partials/CreateOrUpdate.cshtml Tabstrip wrapper partial for Page create/edit
src/Web/Grand.Web.Store/Areas/Store/Views/Page/List.cshtml Two-tab list view with two grids and paging configuration
src/Web/Grand.Web.Store/Areas/Store/Views/Page/Edit.cshtml Store-area edit view including Copy button/form
src/Web/Grand.Web.Store/Areas/Store/Views/Page/Create.cshtml Store-area create view
src/Web/Grand.Web.Store/Areas/Store/Views/_ViewImports.cshtml Adds Pages models namespace for Store views
src/Web/Grand.Web.Admin/Controllers/PageController.cs Fixes Admin paging by returning paged slice + total
src/Web/Grand.Web.Admin/Areas/Admin/Views/Page/List.cshtml Restores standard pageable config and uses AdminAreaSettings page size settings
src/Tests/Grand.Business.Common.Tests/Services/Seo/SeNameServiceTests.cs Adds regression test for null reserved slugs
src/Business/Grand.Business.Common/Services/Seo/SeNameService.cs Null-safe reserved-slug check to prevent ArgumentNullException

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Web/Grand.Web.Store/Controllers/PageController.cs
Comment thread src/Web/Grand.Web.Store/Controllers/PageController.cs Outdated
Comment thread src/Web/Grand.Web.Store/Areas/Store/Views/Page/Edit.cshtml
Comment thread src/Web/Grand.Web.Store/Controllers/PageController.cs
KrzysztofPajak and others added 7 commits May 12, 2026 05:49
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Upgraded the Scryber.Core NuGet package from version 9.3.1 to 9.5.0 in Directory.Packages.props to include the latest features and fixes.
Changed the permission attribute on the Copy action in PageController from Create to Edit, so only users with Edit permission can access this action.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grad.Web.Store - list of landing pages - column Title should be wider, Grad.Web.Admin - list of landing pages, add new column Title

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 12, 2026

@copilot Grad.Web.Store - list of landing pages - column Title should be wider, Grad.Web.Admin - list of landing pages, add new column Title

Done in commit c424873.

  • Grand.Web.Store list: removed the fixed width: 300 from the Title column so it naturally fills the available horizontal space.
  • Grand.Web.Admin list: added a Title column (after SystemName) which was previously absent from the grid.

@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Web.Admin, list of landing pages - add new columns "Include in the menu" and Visible, remove Password protected

…dmin page list

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/46f4585c-a48d-4ba3-8191-7bf99554e8db

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 12, 2026

@copilot Grand.Web.Admin, list of landing pages - add new columns "Include in the menu" and Visible, remove Password protected

Done in commit 3a642a4. The admin Page list now shows: SystemName → Title → Include in MenuPublished → DisplayOrder. The IsPasswordProtected column has been removed.

@KrzysztofPajak KrzysztofPajak merged commit 3f030ce into develop May 12, 2026
6 checks passed
@KrzysztofPajak KrzysztofPajak deleted the copilot/implement-page-management-ui branch May 12, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants